////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_ALLOCATION_INL__
#define __WN_ALLOCATION_INL__

#include "WNCore/inc/WNBase.h"
#include "WNCore/inc/WNAssert.h"
#include "WNCore/inc/WNFunctions.h"

#ifdef _WN_LINUX
    #include "WNMemory/inc/WNManipulation.h"

    #include <stdlib.h>
#endif

__WN_INLINE WN_VOID* WNMalloc(WN_SIZE_T _size) {
    WN_DEBUG_ASSERT(_size != 0);

    return(malloc(_size));
}

__WN_INLINE WN_VOID* WNCalloc(WN_SIZE_T _count, WN_SIZE_T _size) {
    WN_DEBUG_ASSERT(_count != 0);
    WN_DEBUG_ASSERT(_size != 0);

    return(calloc(_count, _size));
}

__WN_INLINE WN_VOID* WNRealloc(WN_VOID* _memory, WN_SIZE_T _newSize) {
    WN_DEBUG_ASSERT(_newSize != 0);

    return(realloc(_memory, _newSize));
}

__WN_INLINE WN_VOID WNFree(WN_VOID* _memory) {
    WN_DEBUG_ASSERT(_memory != WN_NULL);

    free(_memory);
}

__WN_INLINE WN_VOID* WNAlignedMalloc(WN_SIZE_T _size, WN_SIZE_T _alignment) {
    WN_VOID* memory;

    WN_DEBUG_ASSERT(_size != 0);
    WN_DEBUG_ASSERT(WNIsPowerOfTwo(_alignment));

    #ifdef _WN_WINDOWS
        memory = _aligned_malloc(_size, _alignment);
    #elif defined _WN_LINUX
        if (posix_memalign(&memory, _alignment, _size) != 0) {
            memory = WN_NULL;
        }
    #else
        if (WNIsPowerOfTwo(_alignment)) {
            const WN_VOID* tempMemory = WNMalloc((_size + _alignment - 1) + sizeof(WN_VOID*));

            if (tempMemory == WN_NULL) {
                return(WN_NULL);
            }

            memory = (WN_VOID*)(((WN_SIZE_T)tempMemory + sizeof(WN_VOID*) + _alignment - 1) & ~(_alignment - 1));

            *((WN_VOID**)memory - 1) = tempMemory;
        } else {
            return(WN_NULL);
        }
    #endif

    return(memory);
}

__WN_INLINE WN_VOID* WNAlignedCalloc(WN_SIZE_T _count, WN_SIZE_T _size, WN_SIZE_T _alignment) {
    WN_DEBUG_ASSERT(_count != 0);
    WN_DEBUG_ASSERT(_size != 0);
    WN_DEBUG_ASSERT(WNIsPowerOfTwo(_alignment));

    return(WNAlignedMalloc(_count * _size, _alignment));
}

__WN_INLINE WN_VOID* WNAlignedRealloc(WN_VOID* _memory, WN_SIZE_T _newSize, WN_SIZE_T _alignment) {
    WN_VOID* memory;

    WN_DEBUG_ASSERT(_memory != WN_NULL);
    WN_DEBUG_ASSERT(_newSize != 0);
    WN_DEBUG_ASSERT(WNIsPowerOfTwo(_alignment));

    #ifdef _WN_WINDOWS
        memory = _aligned_realloc(_memory, _newSize, _alignment);
    #else
        if (!WNIsPowerOfTwo(_alignment)) {
            return(WN_NULL);
        }

        if (_memory == WN_NULL) {
            return(WN_NULL);
        }

        memory = WNAlignedMalloc(_newSize, _alignment);

        WNMemCpy(memory, _memory, _newSize);
        WNAlignedFree(_memory);
    #endif

    return(memory);
}

__WN_INLINE WN_VOID WNAlignedFree(WN_VOID* _memory) {
    WN_DEBUG_ASSERT(_memory != WN_NULL);

    #ifdef _WN_WINDOWS
        _aligned_free(_memory);
    #elif defined _WN_LINUX
        free(_memory);
    #else
        WNFree(*((WN_VOID**)_memory - 1));
    #endif
}

#endif // __WN_ALLOCATION_INL__
